/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using Lucene.Net.Support;
using ArrayUtil = Lucene.Net.Util.ArrayUtil;

namespace Lucene.Net.Index
{
    
    /// <summary>This is just a "splitter" class: it lets you wrap two
    /// DocFieldConsumer instances as a single consumer. 
    /// </summary>
    
    sealed class DocFieldConsumers : DocFieldConsumer
    {
        private void  InitBlock()
        {
            docFreeList = new PerDoc[1];
        }
        internal DocFieldConsumer one;
        internal DocFieldConsumer two;
        
        public DocFieldConsumers(DocFieldConsumer one, DocFieldConsumer two)
        {
            InitBlock();
            this.one = one;
            this.two = two;
        }
        
        internal override void  SetFieldInfos(FieldInfos fieldInfos)
        {
            base.SetFieldInfos(fieldInfos);
            one.SetFieldInfos(fieldInfos);
            two.SetFieldInfos(fieldInfos);
        }

        public override void Flush(IDictionary<DocFieldConsumerPerThread, ICollection<DocFieldConsumerPerField>> threadsAndFields, SegmentWriteState state)
        {

            var oneThreadsAndFields = new HashMap<DocFieldConsumerPerThread, ICollection<DocFieldConsumerPerField>>();
            var twoThreadsAndFields = new HashMap<DocFieldConsumerPerThread, ICollection<DocFieldConsumerPerField>>();
            
            foreach(var entry in threadsAndFields)
            {
                DocFieldConsumersPerThread perThread = (DocFieldConsumersPerThread) entry.Key;
                ICollection<DocFieldConsumerPerField> fields = entry.Value;

                IEnumerator<DocFieldConsumerPerField> fieldsIt = fields.GetEnumerator();
                ICollection<DocFieldConsumerPerField> oneFields = new HashSet<DocFieldConsumerPerField>();
                ICollection<DocFieldConsumerPerField> twoFields = new HashSet<DocFieldConsumerPerField>();
                while (fieldsIt.MoveNext())
                {
                    DocFieldConsumersPerField perField = (DocFieldConsumersPerField) fieldsIt.Current;
                    oneFields.Add(perField.one);
                    twoFields.Add(perField.two);
                }
                
                oneThreadsAndFields[perThread.one] = oneFields;
                twoThreadsAndFields[perThread.two] = twoFields;
            }
            
            
            one.Flush(oneThreadsAndFields, state);
            two.Flush(twoThreadsAndFields, state);
        }

        public override void  CloseDocStore(SegmentWriteState state)
        {
            try
            {
                one.CloseDocStore(state);
            }
            finally
            {
                two.CloseDocStore(state);
            }
        }
        
        public override void  Abort()
        {
            try
            {
                one.Abort();
            }
            finally
            {
                two.Abort();
            }
        }
        
        public override bool FreeRAM()
        {
            bool any = one.FreeRAM();
            any |= two.FreeRAM();
            return any;
        }
        
        public override DocFieldConsumerPerThread AddThread(DocFieldProcessorPerThread docFieldProcessorPerThread)
        {
            return new DocFieldConsumersPerThread(docFieldProcessorPerThread, this, one.AddThread(docFieldProcessorPerThread), two.AddThread(docFieldProcessorPerThread));
        }
        
        internal PerDoc[] docFreeList;
        internal int freeCount;
        internal int allocCount;
        
        internal PerDoc GetPerDoc()
        {
            lock (this)
            {
                if (freeCount == 0)
                {
                    allocCount++;
                    if (allocCount > docFreeList.Length)
                    {
                        // Grow our free list up front to make sure we have
                        // enough space to recycle all outstanding PerDoc
                        // instances
                        System.Diagnostics.Debug.Assert(allocCount == 1 + docFreeList.Length);
                        docFreeList = new PerDoc[ArrayUtil.GetNextSize(allocCount)];
                    }
                    return new PerDoc(this);
                }
                else
                    return docFreeList[--freeCount];
            }
        }
        
        internal void  FreePerDoc(PerDoc perDoc)
        {
            lock (this)
            {
                System.Diagnostics.Debug.Assert(freeCount < docFreeList.Length);
                docFreeList[freeCount++] = perDoc;
            }
        }
        
        internal class PerDoc:DocumentsWriter.DocWriter
        {
            public PerDoc(DocFieldConsumers enclosingInstance)
            {
                InitBlock(enclosingInstance);
            }
            private void  InitBlock(DocFieldConsumers enclosingInstance)
            {
                this.enclosingInstance = enclosingInstance;
            }
            private DocFieldConsumers enclosingInstance;
            public DocFieldConsumers Enclosing_Instance
            {
                get
                {
                    return enclosingInstance;
                }
                
            }
            
            internal DocumentsWriter.DocWriter one;
            internal DocumentsWriter.DocWriter two;
            
            public override long SizeInBytes()
            {
                return one.SizeInBytes() + two.SizeInBytes();
            }
            
            public override void  Finish()
            {
                try
                {
                    try
                    {
                        one.Finish();
                    }
                    finally
                    {
                        two.Finish();
                    }
                }
                finally
                {
                    Enclosing_Instance.FreePerDoc(this);
                }
            }
            
            public override void  Abort()
            {
                try
                {
                    try
                    {
                        one.Abort();
                    }
                    finally
                    {
                        two.Abort();
                    }
                }
                finally
                {
                    Enclosing_Instance.FreePerDoc(this);
                }
            }
        }
    }
}